Letztes Update: 25.06.2015
Behandelte Befehle: FadeTransition, TranslateTransition, ParallelTransition, initModality, showAndWait, WebView

Im dritten Teil der GUI-Serie beschäftigen wir uns mit Bewegung, der Einbindung von HTML und der Verwendung mehrerer Fenster.

24.1 Webseiten

JavaFX macht es Ihnen leicht, Webseiten darzustellen. Es verwendet dafür eine Open-Source-Software namens WebKit, die auch unter der Haube von dem Browser Safari von Apple steckt. Das bedeutet, dass HTML, CSS und JavaScript ohne Probleme dargestellt bzw. verarbeitet werden.

Zunächst müssen wir strikt unterscheiden zwischen der Browserfunktionalität und der Ansicht. Die Browserfunktionalität ist in der Klasse WebEngine enthalten (man sagt auch, die Funktionalität ist "gekapselt", d.h. deutlich abgetrennt von anderen Funktionen, z.B. zur Darstellung). Wichtige Funktionen von WebEngine sind:

  • Neue Seite anhand von Adresse laden
  • Aktuelles HTML-Dokument speichern/verwalten
  • Aktuelles Adresse (URL) speichern/verwalten
  • Historie von besuchten Adressen speichern
  • JavaScript-Code ausführen

Die Klasse WebView hingegen ist für die Darstellung der Seite zuständig und kann wie ein Button oder ein Label überall eingebunden werden. Mit der Methode getEngine() können Sie auf die WebEngine zugreifen.

Die einfachste Möglichkeit ist, einen WebView in die Szene zu packen und dann mit load eine Webseite aufzurufen:

public class WebViewTest extends Application {

@Override
public void start(Stage stage) {
WebView webView = new WebView();
webView.getEngine().load("http://www.hs-augsburg.de");

Scene scene = new Scene(webView, 600, 400);
stage.setTitle("WebView");
stage.setScene(scene);
stage.show();
}
}

Sie erhalten ein vollwertiges Browserfenster und können dort natürlich auch mit Hilfe von Links auf andere Inhalte navigieren.

Sie können mit eigenen Buttons etc. auch einen kompletten Browser bauen. Probieren Sie zum Beispiel, einen "Back"-Button einzubauen und fernsteuern Sie damit die "Engine" (über webView.getEngine).

24.2 Animationen und Effekte

Übergänge mit Transition

Ein Übergang (engl. transition) bedeutet, dass eine Eigenschaft von Wert A zu Wert B übergeht, in einer bestimmten Zeit. Zum Beispiel kann man die Eigenschaft "deckend" (das Gegenteil von "transparent") von 1 (voll deckend) zu 0 (ganz transparent) in einer Sekunde laufen lassen. Das wäre ein Fade-Out.

JavaFX stellt dafür die Klasse FadeTransition bereit. Nehmen wir an, die Variable button enthielte ein Button-Objekt:

FadeTransition ft = new FadeTransition(Duration.millis(1000), button);
ft.setFromValue(1.0);
ft.setToValue(0);
ft.play();

Der obige Code würde den Button ausfaden lassen. Hier der vollständige Code. Beim Klicken verschwindet der Button langsam:

public class TransitionDemo extends Application {

public void start(Stage stage) {
Button button = new Button();
button.setText("Say 'Hello World'");
button.setOnAction(e -> {

FadeTransition ft =
new FadeTransition(Duration.millis(1000), button);
ft.setFromValue(1.0);
ft.setToValue(0);
ft.play();

});

StackPane root = new StackPane();
root.getChildren().add(button);

Scene scene = new Scene(root, 300, 250);
stage.setTitle("Hello World!");
stage.setScene(scene);
stage.show();
}
}

Sie können den Button auch pulsieren lassen, indem Sie auf "unendlich zyklen" stellen und ein "Auto-Reverse" einstellen. Letzteres stellt sicher, dass einmal ausgefadet, dann eingefadet wird.

button.setOnAction(e -> {

FadeTransition ft =
new FadeTransition(Duration.millis(1000), button);
ft.setFromValue(1.0);
ft.setToValue(0);
ft.setCycleCount(Timeline.INDEFINITE);
ft.setAutoReverse(true);
ft.play();

});

Animationen und neue Mausevents

Anstatt ein Kontrollelement ein- oder auszufaden, können Sie es auch animieren. Dafür gibt es z.B. die Klasse TranslateTransition.

Stellen Sie sich vor, Sie möchten eine Leiste mit Buttons nur dann in das Bild schieben, sofern sich die Maus im Fenster befindet.

Mauszeiger außerhalb:

Mauszeiger im Fenster:

Anders formuliert: die Buttonleiste hat zwei Animationen, eine zum Hineinfahren (transShow), eine zum Hinausfahren (transHide).

Wir möchten jetzt auf zwei neue Arten von Events regieren. Erstens auf das Event "Mauszeiger kommt ins Fenster", dann wird die Animation transShow gestartet. Zweitens auf das Event "Mauszeiger verlässt Fenster", dann wird transHide gespielt.

public class RolloutMenu extends Application {

@Override
public void start(Stage stage) {

Label label = new Label("---");

Button b1 = new Button("f");
b1.setId("facebook");
Button b2 = new Button("G+");
b2.setId("googleplus");
Button b3 = new Button("Tw");
b3.setId("twitter");

VBox buttons = new VBox(b1, b2, b3);
buttons.setSpacing(5);

TranslateTransition transShow =
new TranslateTransition(Duration.seconds(1), buttons);
transShow.setFromX(-70);
transShow.setToX(-5);

TranslateTransition transHide =
new TranslateTransition(Duration.seconds(1), buttons);
transHide.setFromX(-5);
transHide.setToX(-70);
transHide.play();

StackPane root = new StackPane();
root.getChildren().add(buttons);
root.getChildren().add(label);

// Aktionen

root.setOnMouseEntered(e -> {
transShow.play();
});

root.setOnMouseExited(e -> {
transHide.play();
});

Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add("style.css");

stage.setTitle("rollout");
stage.setScene(scene);
stage.show();
}
}

Das dazugehörige CSS-File sieht wie folgt aus:

.button {
-fx-font-family: Heletica;
-fx-font-size: 30;
-fx-font-weight: bold;
-fx-border-style: none;
-fx-border-radius: 0;
-fx-text-fill: white;
}


#facebook {
-fx-background-color: blue;
}

#googleplus {
-fx-background-color: red;
-fx-font-size: 20;
}

#twitter {
-fx-background-color: lightblue;
-fx-font-size: 20;
}

Wenn Sie möchten, dass ein Button auf ein Maus-Rollover reagiert, können Sie die Pseudo-Klasse .button:hover verwenden. Das dort definierte Styling wird angewandt, sobald die Maus über dem Button "hovert".

.button:hover {
-fx-text-fill: yellow;
-fx-border-style: solid;
-fx-border-color: black;
}

So sieht das dann aus (Mauszeiger über dem zweiten Button):

Sie können noch viel aufwändigere Animationen durchführen. Wenn Sie komplexe Wege animieren wollen, sollten Sie sich die Klasse PathTransition ansehen.

Kombinationen

Mit der Klasse ParallelTransition lassen sich mehrere Übergänge kombinieren. Man schnürt quasi ein Paket von Übergängen, die alle gleichzeitig (parallel) ablaufen.

Wenn wir im obigen Beispiel die Buttonleiste nicht nur animieren wollen, sondern auch ein- und ausfaden, dann ändert sich der Code wie folgt:

public class RolloutMenu extends Application {

@Override
public void start(Stage stage) {

Label label = new Label("---");

Button b1 = new Button("f");
b1.setId("facebook");
Button b2 = new Button("G+");
b2.setId("googleplus");
Button b3 = new Button("Tw");
b3.setId("twitter");

VBox buttons = new VBox(b1, b2, b3);
buttons.setSpacing(5);

TranslateTransition transShow =
new TranslateTransition(Duration.seconds(1), buttons);
transShow.setFromX(-70);
transShow.setToX(-5);

FadeTransition transFadeIn =
new FadeTransition(Duration.seconds(1), buttons);
transFadeIn.setFromValue(0);
transFadeIn.setToValue(1.0);

ParallelTransition transIn =
new ParallelTransition(transShow, transFadeIn);

TranslateTransition transHide =
new TranslateTransition(Duration.seconds(1), buttons);
transHide.setFromX(-5);
transHide.setToX(-70);
transHide.play();

FadeTransition transFadeOut =
new FadeTransition(Duration.seconds(1), buttons);
transFadeOut.setFromValue(1.0);
transFadeOut.setToValue(0);

ParallelTransition transOut =
new ParallelTransition(transHide, transFadeOut);

StackPane root = new StackPane();
root.getChildren().add(buttons);
root.getChildren().add(label);

root.setOnMouseEntered(e -> {
transIn.play();
});

root.setOnMouseExited(e -> {
transOut.play();
});

Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add("style.css");

stage.setTitle("rollout");
stage.setScene(scene);
stage.show();
}
}

Es gibt eine ähnliche Klasse SequentialTransition, welche die zusammengeschnürten Objekte nicht parallel, sondern hintereinander abspielt. Die flexibelste Art der Animation funktioniert mit Keyframes. In JavaFX nennt man das Timeline Animation. Hier können beliebige Eigenschaften (Position, Größe, Farbe etc.) in Keyframes entlang einer Timeline gepackt werden. Bei Interesse sehen Sie sich die Oracle-Dokumentation an.

Effekte

Sie können verschiedene Effekte auf Ihre Komponenten loslassen: Weichzeichner, Spiegelung, Schatten... Wir zeigen hier nur das Prinzip:

Button b = new Button();
b.setText("Say 'Hello World'");

BoxBlur blur = new BoxBlur();
b.setOnAction(e -> {

if (b.getEffect() == null) {
b.setEffect(blur);
} else {
b.setEffect(null);
}

});

Probieren Sie außerdem die Klassen: GaussianBlur, DropShadow, InnerShadow.

Mehr dazu unter JavaFX: Transformations, Animations, and Visual Effects

24.3 Mehrere Fenster

Bislang können wir nur ein einziges Fenster öffnen. Eine Applikation hat aber in der Regel viele Fenster, einige sind permanent geöffnet (z.B. die vielen Fenster bei Photoshop), andere treten nur kurzzeitig in Erscheinung, z.B. um ein Passwort abzufragen.

Fenster, die nur kurz in Erscheinung treten, um eine minimale Information abzufragen, nennt man Dialogfenster. Beispiele sind: Bestätigung, dass eine Datei überschrieben werden darf, Passworteingabe, Eingabe eines Dateinamens.

Dialogfenster können auch alle anderen Fenster sperren, d.h. man kann in der Hauptapplikation erst weiterarbeiten, wenn das Dialogfenster geschlossen ist. In solch einem Fall spricht man von einem Modalfenster.

In diesem Kapitel beschäftigen wir uns mit Dialogfenstern und mit modalen Dialogfenstern.

Neues Fenster, neue Klasse

Um ein neues Fenster zu erstellen, kreieren wir eine neue Unterklasse von Stage. Nehmen wir an, wir haben einen Facebook-Button auf unserer Haupt-GUI. Wenn wir den Button drücken, soll sich ein Share-Dialog öffnen:

Wir erstellen also zunächst eine neue Klasse als Unterklasse von Stage:

public class FacebookDialog extends Stage {

// Konstruktor
public FacebookDialog() {
super(); // Konstruktor von Stage wird aufgerufen
setTitle("Facebook Share");

...
}
}

Wir rufen mit super() den Konstruktor von Stage auf, da dort evtl. wichtige Einstellungen getroffen werden. Mit setTitle() setzen wir den Titel des Fensters, der in dem Balken oben auf dem Fenster erscheint.

Mit dem Befehl initModality() können wir erzwingen, dass die Hauptapplikation gesperrt ist, solange unser Dialog offen ist. Wir wählen dazu die Option Modality.APPLICATION_MODAL. Ferner wollen wir den Text, den wir eingeben, in einer Variablen speichern, damit wir den Text später aus dem Dialogfenster "rausholen" können. Die Klasse könnte wie folgt aussehen:

public class FacebookDialog extends Stage {

private String text = "";

public FacebookDialog() {
super();
setTitle("Facebook");
initModality(Modality.APPLICATION_MODAL);

Label label = new Label("Say something: ");
TextField textfield = new TextField();
VBox main = new VBox(label, textfield);
main.setSpacing(10);

Button ok = new Button("OK");
Button cancel = new Button("Cancel");

textfield.setOnAction(e -> {
text = textfield.getText();
close();
});


ok.setOnAction(e -> {
text = textfield.getText();
close();
});

cancel.setOnAction(e -> {
close();
});

HBox buttons = new HBox(ok, cancel);
buttons.setSpacing(5);
buttons.setAlignment(Pos.CENTER_RIGHT);
buttons.setPadding(new Insets(15, 0, 0, 0));

BorderPane pane = new BorderPane(main);
pane.setBottom(buttons);
pane.setPadding(new Insets(20));

Scene scene = new Scene(pane, 350, 120);
setScene(scene);
}

public String getText() {
return text;
}
}

Fenster aufrufen und warten

Wir nehmen uns das Beispiel der Buttons von 26.2 und wollen beim Klick auf den Facebook-Button unseren Dialog öffnen:

Dazu hängen wir folgenden Code an:

public class RolloutMenu extends Application {

@Override
public void start(Stage stage) {

Label label = new Label("---");

Button b1 = new Button("f");
b1.setId("facebook");
Button b2 = new Button("G+");
b2.setId("googleplus");
Button b3 = new Button("Tw");
b3.setId("twitter");

b1.setOnAction(e -> {
FacebookDialog dialog = new FacebookDialog();

dialog.showAndWait(); // BLOCKIERT!!!

System.out.println("weiter geht's!");
label.setText(dialog.getText());
});

VBox buttons = new VBox(b1, b2, b3);
buttons.setSpacing(5);

TranslateTransition transShow =
new TranslateTransition(Duration.seconds(1), buttons);
transShow.setFromX(-70);
transShow.setToX(-5);

TranslateTransition transHide =
new TranslateTransition(Duration.seconds(1), buttons);
transHide.setFromX(-5);
transHide.setToX(-70);
transHide.play();

StackPane root = new StackPane();
root.getChildren().add(buttons);
root.getChildren().add(label);

root.setOnMouseEntered(e -> {
transShow.play();
});

root.setOnMouseExited(e -> {
transHide.play();
});

Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add("style.css");

stage.setTitle("rollout");
stage.setScene(scene);
stage.show();
}
}

Besonders wichtig ist die Verwendung von showAndWait. Dies führt dazu, dass Java an dieser Code-Zeile wartet, bis das Dialogfenster geschlossen wird. Da wir den Text des Users in unser Hauptfenster einfügen wollen, müssen wir warten, bis der Text vorliegt. Wenn wir diese Anforderung nicht haben, können wir das übliche show verwenden.

Der Dialog sieht wie folgt aus:

Externe Bibliothek ControlsFX

Hinweis: Ab Java Version 8u40 sind einige Dialoge von ControlsFX direkt in Java verbaut und das hier dargestellte Beispiel funktioniert anders als hier dargestellt. Dieser Abschnitt wird demnächst überarbeitet.

Siehe auch JavaFX Dialogs (official)

Eine Reihe von Standard-Dialogen gibt es bereits fertig programmiert. In diesem Abschnitt sehen wir uns an, wie man eine solche externe Bibliothek anwendet. Wir betrachten dazu "ControlsFX", ein externes Projekt (also nicht von Oracle), welches sowohl Dialogfenster als auch weitere Kontrollelemente (spezielle Buttons etc.) zur Verfügung stellt.

Sie finden das externe Projekt unter controlsfx.org:

Wenn Sie auf den Link unter "Getting ControlsFX" klicken, erhalten Sie ein ZIP-File, das sie auspacken. Es enthält:

Zunächst einmal ist ein Blick auf die Lizenzvereinbarung wichtig. Sie bestimmt, in welchen Fällen Sie die Bibliothek verwenden dürfen, ohne Gebühren zu zahlen. Es gibt mehrere Standard-Lizenzmodelle, für die Sie Text-Vorlagen im Internet finden. ControlsFX verwendet die BSD 3-Clause License. Die wesentlichen Punkte stehen in der Datei "license.txt". Sie sagt im wesentlichen, dass Sie bei Verwendung und Weitergabe lediglich das Copyright von ControlsFX angeben müssen. Dann dürfen Sie den Code in Ihr Projekt einbauen und sogar verändern, egal ob Sie anschließend Ihr Programm verkaufen oder kostenlos weitergeben.

Um einen Eindruck von der Mächtigkeit des Pakets zu bekommen, können Sie "controlsfx-samples-8.0.6.jar" starten. Versuchen Sie, die Datei per Doppelclick zu starten. Wenn das nicht geht, dann per Konsole (unter Mac OS: Terminal starten, unter Windows: Command Shell starten, z.B. mit Windows-Taste + "R", dann "CMD" tippen):

java -jar controlsfx-samples-8.0.6.jar

Jetzt können Sie in der linken Leiste sämtliche zur Verfügung gestellten Kontrollen und Fenster ausprobieren. Uns interessieren hauptsächlich die "Dialogs":

Praktisch ist, dass man sich den Quelltext direkt unter dem Reiter "Source" ansehen kann.

Wir wollen jetzt einen Bestätigung-Dialog programmieren. Dazu erstellen wir ein neues JavaFX-Projekt mit Namen "ControlsFXTest".

Als nächstes müssen wir die Bibliothek von ControlsFX einbinden. Dazu gehen wir in das Projektverzeichnis und erstellen ein neues Unterverzeichnis "lib" (für library, engl. für Bibliothek):

In dieses Verzeichnis kopieren Sie die Datei "controlsfx-8.0.6.jar", die Sie runtergeladen hatten. Diese Datei enthält alle Klassen - in komprimierter Form - die das Team von ControlsFX geschrieben hat. Diese Klassen sind, genauso wie die Java-Klassen, in Paketen organisiert.

Damit auch NetBeans weiß, dass dieses jar-File verwendet werden soll, müssen Sie dies in den Eigenschaften Ihres Projekts angeben. Dazu öffnen Sie mit Rechtsklick auf den Projektnamen im Projektfenster (links) und dann Properties das Eigenschafts-Fenster:

Anschließend klicken Sie auf Add JAR/Folder und wählen das eben hinzugefügte jar-File aus (mit Option "relative path"). Anschließend stehen Ihnen alle Klassen von ControlsFX zur Verfügung.

(Dieser Mechanismus gilt für alle externen Bibliotheken, die Sie einbinden möchten.)

Im folgenden sehen Sie, wie man einen Dialog aufruft. Wir verwenden das Standard-Programm, das NetBeans zu Beginn generiert und zeigen einen Dialog, sobald man auf den Button klickt. Dazu verwendet man die ControlsFX-Klasse Dialogs aus dem Paket org.controlsfx.dialog. Jede der Methoden wie owner(), style(), title() etc. gibt wieder ein Objekt zurück, so dass Sie einfach die Methodenaufrufe aneinanderhängen können.

Der finale Aufruf von showConfirm() ist blockierend, d.h. der Code hält an dieser Stelle an, solange bis das Dialogfenster wieder geschlossen ist.

Hinweis: Ab Java Version 8u40 sind einige Dialoge von ControlsFX direkt in Java verbaut und das hier dargestellte Beispiel funktioniert anders als hier dargestellt. Dieser Abschnitt wird demnächst überarbeitet.

public class ControlsFXTest extends Application {

@Override
public void start(Stage stage) {
Button b = new Button();
b.setText("Say 'Hello World'");
b.setOnAction(new EventHandler() {

@Override
public void handle(ActionEvent event) {

		Action response = Dialogs.create()
						.owner(stage)
						.style(DialogStyle.NATIVE)
						.title("You do want dialogs right?")
						.message("Just wondering...")
						.showConfirm();

		if (response == Dialog.Actions.YES) {
				System.out.println("yes");
		} else {
				System.out.println("cancelled or no");
		}
}
});

StackPane root = new StackPane();
root.getChildren().add(b);

Scene scene = new Scene(root, 300, 250);

stage.setTitle("Hello World!");
stage.setScene(scene);
stage.show();
}
}

Das ganze sieht so aus:

Wie Sie sich denken können, setzt jeder Methodenaufruf (bis auf den letzten) eine Eigenschaft des Dialogs:

  • owner: legt das Fenster fest, das den Dialog "beinhaltet"
  • style: hier kann man angeben, ob die Fensteroptik derjenigen des Betriebssystems (Mac, Win) entsprechen soll
  • title: Text im Fensterbalken oben
  • message: Text in der Mitte des Dialogs

Der finale Aufruf showConfirm() zeigt nicht nur das Fenster und blockiert, sondern gibt, sobald das Fenster zu ist, auch einen Wert zurück. Dieses Rückgabeobjekt vom Typ Action speichert, welchen Button der User gedrückt hat

Übungsaufgabe

Text Reader

Schreiben Sie ein Programm, mit dem Sie Textdateien laden und betrachten können. Damit hätten Sie praktisch einen "halben" Texteditor.

Ihr Hauptfenster hat drei Bereiche: einen Titel, einen Textbereich (zunächst leer) und die Button-Leiste (Load und Close). Der Textbereich ist vom Typ TextArea, schauen Sie sich dazu die entsprechende API an.

Zusätzlich benötigen Sie ein Fenster, das nach einem Dateinamen fragt, sobald Sie auf "Load" klicken:

Dies Fenster (zum Beispiel könnten Sie es LoadDialog nennen) öffnet wiederum, wenn Sie "Browse" anklicken einen FileChooser (siehe Kap.25). Dabei sollten Sie anbieten, nach .txt Dateien zu filtern. Das ausgewählte File wird in das Textfeld geschrieben.

Sobald im Dialog OK geklickt wird, lädt Ihr Hauptprogramm die ausgewählte Datei (sofern sie existiert) und zeigt diese im Textbereich an:

Benutzen Sie ein CSS für beide Fenster, um z.B. die Buttons einheitlich zu stylen.

Hinweise: Sie können den Cursor in einer TextArea und in einem TextField mit der Methode positionCaret() setzen (Eingabe ist eine Zahl, z.B. 0 für ganz oben/vorn).

24.4 Videos

Videos können problemlos in eine GUI integriert werden. Das Integrieren von Videos in Java war schon immer schwierig und ist in JavaFX zwar besser, aber noch nicht ideal. Größtes Problem ist die Frage, welche Formate abgespielt werden können.

Eine Videodatei wird zunächst in ein Objekt vom Typ Media gepackt. Anschließend wird der eigentliche Videoplayer erzeugt als Objekt vom Typ MediaPlayer.

Schließlich muss noch eine grafische Komponente erzeugt, die das Videobild anzeigt. Diese ist vom Typ MediaView. Grund für diese Trennung ist, dass ein Videoplayer mehrere Views gleichzeitig bedienen können sollte, wenn man das selbe Video mehrfach, parallel anzeigen lassen möchte.

Player und Events

Ein MediaPlayer-Objekt erlaubt Ihnen, auf bestimmte Events zu regieren, z.B.

  • Player ist hochgefahren und in Bereitschaft
  • Player beginnt Abspielen des aktuellen Videos
  • Player wurde pausiert
  • Player hat Ende des Videos erreicht

Selbst für einen einfachen Videoplayer benötigen wir bereits das Event "in Bereitschaft" (engl. ready), denn wir wollen unser Fenster erst dann zeigen, wenn der Player in Bereitschaft ist und somit die visuelle Komponente (MediaView) eine sinnvolle Größe (Breite und Höhe) angeben kann.

Ein einfacher Videoplayer sieht wie folgt aus. Sie müssen einen Pfad zum Video für den Konstruktor von Media angeben. Diese Version funktioniert noch nicht, weil die das Fenster (Stage) noch nicht mit show() zeigen.

public class VideoPlayerSimple extends Application {

@Override
public void start(Stage stage) {

// Medium
Media media = new Media("file:////Users/kipp/fxvideos/test.mp4");

// Player und View erzeugen
MediaPlayer player = new MediaPlayer(media);
MediaView mediaView = new MediaView(player);

// Player soll direkt starten
player.setAutoPlay(true);

// Layout
StackPane root = new StackPane(mediaView);
Scene scene = new Scene(root);
stage.setTitle("Video Player");
stage.setScene(scene);
}
}

Jetzt hängen wir Funktionen für das Event-Handling an den Player. Im Gegensatz zu Buttons haben die Handler keinen Parameter, daher also () -> {} statt e -> {}. Zum Beispiel:

player.setOnPlaying(() -> {
System.out.println("> Player is playing");
});

Wir nutzen das Event "Player ist jetzt bereit", um das Fenster (Stage) zu zeigen (show):

player.setOnReady(() -> {

// Fenster erst zeigen, wenn Player bereit ist,
// damit die Fenstergröße korrekt eingestellt wird
stage.show();

});

Der vollständige Player mit entsprechenden Ausgaben - für die Events ready, playing und end of media - sieht dann so aus:

public class VideoPlayerSimple extends Application {

@Override
public void start(Stage stage) {

// Medium
Media media = new Media("file:////Users/kipp/fxvideos/test.mp4");

// Player und View erzeugen
MediaPlayer player = new MediaPlayer(media);
MediaView mediaView = new MediaView(player);

// Player soll direkt starten
player.setAutoPlay(true);

// Layout
StackPane root = new StackPane(mediaView);
Scene scene = new Scene(root);
stage.setTitle("Video Player");
stage.setScene(scene);

// Aktionen
player.setOnReady(() -> {
System.out.println("> Player is ready");

// Fenster erst zeigen, wenn Player bereit ist,
// damit die Fenstergröße korrekt eingestellt wird
stage.show();

System.out.println("  Video duration: " + player.getTotalDuration().toSeconds() + " sec");
System.out.println("  Video screen size: " + media.getWidth() + "x" + media.getHeight());
});

player.setOnPlaying(() -> {
System.out.println("> Player is playing");
});

player.setOnEndOfMedia(() -> {
System.out.println("> Player has reached end of movie");
});
}
}

Kontrollflächen

Mit dem bisherigen Player können sie lediglich ein vorgegebenes Video von Anfang bis Ende durchspielen. In der Regel möchten Sie noch die folgenden Möglichkeiten haben:

  • Pausieren/Weiterspielen
  • Anhalten
  • Zeitleiste mit aktueller Position

Das realisieren Sie mit den entsprechenden Kommandos der Klasse MediaPlayer, die Sie an die Buttons hängen:

playB.setOnAction(e -> {
player.play();
});

pauseB.setOnAction(e -> {
player.pause();
});

stopB.setOnAction(e -> {
player.stop();
});

Timeline

Schlißelich ist es noch üblich, einen Schieberegler (Slider) als Darstellung der Zeitposition im Video und als Schaltelement zum Springen im Video einzusetzen

Slider timeSlider = new Slider();

Zusätzlich möchten Sie die Zeit als Sekundenangabe textuell anzeigen:

Label timeLabel = new Label("--- s");

Den Slider können Sie erst konfigurieren, wenn das Video geladen und seine Länge bekannt ist.

player.setOnReady(() -> {
timeSlider.setMin(0.0);
timeSlider.setValue(0.0);
timeSlider.setMax(player.getTotalDuration().toSeconds());
stage.show();
});

Sowohl Slider als auch Label müssen während des Abspielens des Videos immer wieder angepasst werden. Dazu lauschen wir auf eine "Eigenschaft" (property) des MediaPlayer-Objekts. Wir können einen Lambda-Ausdrück an das Event "Eigenschaft ändert sich" hängen. Man nennt diesen Ausdruck auch einen "Listener". Dieser Listener bekommt drei Parameter, die in dem Lambda vorkommen müssen:

player.currentTimeProperty().addListener((observableValue, oldValue, newValue) -> {
timeLabel.setText(newValue.toSeconds() + " s");

if (!timeSlider.isValueChanging()) {
timeSlider.setValue(newValue.toSeconds());
}
});

Hier der vollständige Code:

public class VideoPlayer extends Application {

private boolean playBeforeDrag = false;

@Override
public void start(Stage stage) {

// Player
Media media = new Media("file:////Users/kipp/fxvideos/test2.mp4");
MediaPlayer player = new MediaPlayer(media);
MediaView mediaView = new MediaView(player);
player.setAutoPlay(true);

// Time Slider

Label timeLabel = new Label("--- s");
Slider timeSlider = new Slider();


// Buttons
Button playB = new Button("Play");
Button pauseB = new Button("Pause");
Button stopB = new Button("Stop");
Button closeB = new Button("Close");

HBox controlPane = new HBox(playB, pauseB, stopB, closeB,
		timeSlider, timeLabel);
controlPane.setSpacing(10);
controlPane.setPadding(new Insets(10));

// Layout
BorderPane mainPane = new BorderPane();
mainPane.setCenter(mediaView);
mainPane.setBottom(controlPane);

Scene scene = new Scene(mainPane, 800, 600);
stage.setTitle("Video Player");
stage.setScene(scene);

// Aktionen Player
player.setOnReady(() -> {
timeSlider.setMin(0.0);
timeSlider.setValue(0.0);
timeSlider.setMax(player.getTotalDuration().toSeconds());
stage.show();
});

player.currentTimeProperty().addListener((observableValue, oldValue, newValue) -> {
timeLabel.setText(newValue.toSeconds() + " s");

if (!timeSlider.isValueChanging()) {
		timeSlider.setValue(newValue.toSeconds());
}
});

timeSlider.setOnMousePressed(e -> {
playBeforeDrag = player.getStatus() == MediaPlayer.Status.PLAYING;
player.seek(Duration.seconds(timeSlider.getValue()));
player.pause();
});

timeSlider.setOnMouseDragged(e -> {
player.seek(Duration.seconds(timeSlider.getValue()));
});

timeSlider.setOnMouseReleased(e -> {
if (playBeforeDrag) {
		player.play();
}
});

playB.setOnAction(e -> {
player.play();
});

pauseB.setOnAction(e -> {
player.pause();
});

stopB.setOnAction(e -> {
player.stop();
});

closeB.setOnAction(e -> {
Platform.exit();
});
}
}

24.5 Weiterführende Links